﻿#region Usings

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

#endregion

namespace Kazkommertsbank.Framework.Common
{
    public class UrlHelper
    {
        /// <summary>
        ///   Returns the full, absolute URL of your site http://mydomain.com:8080/
        /// </summary>
        public static Uri ServerUri
        {
            get
            {
                string port = HttpContext.Current.Request.ServerVariables["SERVER_PORT"];
                if(port == null || port == "80" || port == "443")
                    port = "";
                else
                    port = ":" + port;

                string protocol = HttpContext.Current.Request.ServerVariables["SERVER_PORT_SECURE"];
                if(protocol == null || protocol == "0")
                    protocol = "http://";
                else
                    protocol = "https://";

                string sOut = protocol + HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + port;
                return new Uri(sOut);
            }
        }

        /// <summary>
        ///   Returns the absolute Uri to the application root eg: http://mydomain.com/Webstore/
        /// </summary>
        public static Uri ApplicationUri
        {
            get { return new Uri(Combine(ServerUri.OriginalString, HttpContext.Current.Request.ApplicationPath)); }
        }

        /// <summary>
        ///   Returns a local file system path to your application root D:\inetpub\wwwroot\WebStore\
        /// </summary>
        public static string ApplicationPath
        {
            get { return HttpContext.Current.Request.PhysicalApplicationPath; }
        }

        /// <summary>
        ///   Returns the Web server relative path to your application root eg: /WebStore/
        /// </summary>
        public static string ApplicationUrlVirtual
        {
            get { return HttpContext.Current.Request.ApplicationPath; }
        }

        /// <summary>
        ///   Returns the full file system path to the currently executing script D:\inetpub\wwwroot\WebStore\Item.aspx
        /// </summary>
        public static string PagePath
        {
            get { return HttpContext.Current.Request.PhysicalPath; }
        }

        /// <summary>
        ///   Returns the full absolute uri of the path to the currently executing script http://mydomain.com/Webstore/item.aspx
        /// </summary>
        public static Uri PageUri
        {
            get { return new Uri(Combine(ServerUri.OriginalString, HttpContext.Current.Request.CurrentExecutionFilePath)); }
        }

        /// <summary>
        ///   Returns the virtual path to the currently executing script relative to the Web Server root /WebStore/item.aspx
        /// </summary>
        public static string PageUrlVirtual
        {
            get { return HttpContext.Current.Request.CurrentExecutionFilePath; }
        }

        /// <summary>
        ///   Returns the fully qualified URL including domain and protocol http://mydomain.com/Webstore/item.aspx?sku=WWHELP30
        /// </summary>
        public static Uri PageParamUri
        {
            get { return HttpContext.Current.Request.Url; }
        }

        /// <summary>
        ///   Returns the application relative URL including querystring or pathinfo /WebStore/item.aspx?sku=WWHELP30
        /// </summary>
        public static string PageParamUrlVirtual
        {
            get { return HttpContext.Current.Request.RawUrl; }
        }

        /// <summary>
        ///   Return physical path on server corresponding to url
        /// </summary>
        /// <param name="url"> /WebStore/item.aspx </param>
        /// <returns> D:\inetpub\wwwroot\WebStore\Item.aspx </returns>
        public static string MapPath(string url)
        {
            return HttpContext.Current.Server.MapPath(url);
        }

        /// <summary>
        ///   Converts a URL into one that is usable on the requesting client
        /// </summary>
        /// <remarks>
        ///   Converts ~ to the requesting application path. Mimics the behavior of the Control.ResolveUrl()
        /// </remarks>
        /// <param name="url"> The URL, which might contain ~. </param>
        /// <returns> A resolved URL. If the input parameter <b>url</b> contains ~, it is replaced with the value of the <b>appPath</b> parameter. </returns>
        public static Uri ResolveUrl(string url)
        {
            if(String.IsNullOrEmpty(url))
                throw new ArgumentNullException("url");
            if(url.StartsWith("http", StringComparison.CurrentCultureIgnoreCase) || url.StartsWith("https", StringComparison.CurrentCultureIgnoreCase))
                return new Uri(url);
            if(url[0] != '~' && url[0] != '/')
                throw new ArgumentException("url");

            if(url.Length == 1)
                return ApplicationUri;

            string appPath = ApplicationUri.OriginalString;
            string retPath;
            if(url[0] == '/') // looks like /something
                retPath = Combine(appPath, url);
            else if(url[1] == '/' || url[1] == '\\') // url looks like ~/ or ~\
            {
                if(appPath.Length > 1)
                    retPath = Combine(appPath, url.Substring(2));
                else
                    retPath = "/" + url.Substring(2);
            }
            else // url looks like ~something
            {
                if(appPath.Length > 1)
                    retPath = appPath + "/" + url.Substring(1);
                else
                    retPath = appPath + url.Substring(1);
            }
            return new Uri(retPath);
        }

        /// <summary>
        ///   Converts all links in the html text to short url
        /// </summary>
        /// <param name="htmlText"> html text </param>
        /// <returns> http://foo.com/.../LINK.html </returns>
        public static string ShrinkLinks(string htmlText)
        {
            if(string.IsNullOrEmpty(htmlText))
                return htmlText;

            foreach(Match match in regex.Matches(htmlText))
            {
                htmlText = htmlText.Replace(match.Value,
                                            !match.Value.Contains("://")
                                                ? string.Format(LINK, "http://", match.Value, ShortenUrl(match.Value, 50))
                                                : string.Format(LINK, string.Empty, match.Value, ShortenUrl(match.Value, 50)));
            }

            return htmlText;
        }

        /// <summary>
        ///   Combines 2 url parts
        /// </summary>
        /// <param name="url1"> http://mydomain.com/ </param>
        /// <param name="url2"> /Webstore </param>
        /// <returns> http://mydomain.com/Webstore/ </returns>
        public static string Combine(string url1, string url2)
        {
            return string.Concat(url1, "/", url2).Replace("///", "/").Replace("//", "/").Replace(":/", "://");
        }

        /// <summary>
        ///   Takes a url and adds new supplied query string values and removed supplied removal values.
        /// </summary>
        /// <param name="targetUrl"> The url upon which to work. </param>
        /// <param name="newValues"> Values to add to the query string. </param>
        /// <param name="valuesToRemove"> Values to remove from the query string. </param>
        /// <returns> Returns a query string based on the target Url with additions and subtractions of values. </returns>
        public static string CreateUrl(string targetUrl, Dictionary<string, string> newValues, List<string> valuesToRemove)
        {
            Dictionary<string, string> queryStringValues = new Dictionary<string, string>();

            string targetPath = GetBasePathFromUrl(targetUrl);
            ExtractQueryStringValuesFromUrlString(targetUrl, ref queryStringValues);

            string queryString = CreateQueryString(queryStringValues, newValues, valuesToRemove);

            return CombinePathAndQueryString(targetPath, queryString);
        }

        /// <summary>
        ///   Takes a starting Uri, a target uri, and optional value collections and creates a url based on the targetUrl that has combined the values of the Uri and url string.
        /// </summary>
        /// <param name="currentUri"> The Uri of the page receiving the request. </param>
        /// <param name="targetUrl"> The url of the page which is the destination. </param>
        /// <param name="valuesToAdd"> Optional values to add to the query string. </param>
        /// <param name="valuesToRemove"> Optional values to remove from the query string. </param>
        /// <returns> Returns a query string based on the target url with query string values from the current Uri and supplied parameters. </returns>
        public static string CreateUrl(Uri currentUri, string targetUrl, Dictionary<string, string> valuesToAdd, List<string> valuesToRemove)
        {
            Dictionary<string, string> queryStringValues = new Dictionary<string, string>();

            string targetPath = GetBasePathFromUrl(targetUrl);

            ExtractQueryStringValuesFromUri(currentUri, ref queryStringValues);

            ExtractQueryStringValuesFromUrlString(targetUrl, ref queryStringValues);

            string queryString = CreateQueryString(queryStringValues, valuesToAdd, valuesToRemove);

            return CombinePathAndQueryString(targetPath, queryString);
        }

        // //

        /// <summary>
        ///   Creates a query string from a NameValueCollection of existings query string values (like you would get from Request.QueryString), a dictionary of values to add, and a dictionary of values to remove.
        /// </summary>
        /// <remarks>
        ///   This method will probably be used primarily by the CreateUrl methods. However, they are made public so they can be used if needed.
        /// </remarks>
        /// <param name="existingValues"> NameValueCollection of parameters that you start with. </param>
        /// <param name="newValues"> Parameters that you add to the existing collection. </param>
        /// <param name="valuesToRemove"> Parameters that you want removed from the collection. </param>
        /// <returns> A query string. </returns>
        public static string CreateQueryString(NameValueCollection existingValues, Dictionary<string, string> newValues, List<string> valuesToRemove)
        {
            Dictionary<string, string> values = existingValues.Keys.Cast<string>().ToDictionary(key => key, key => existingValues[key]);

            return CreateQueryString(values, newValues, valuesToRemove);
        }

        /// <summary>
        ///   Creates a query string from a dictionary of existings query string values , a dictionary of values to add, and a dictionary of values to remove.
        /// </summary>
        /// <remarks>
        ///   This method will probably be used primarily by the CreateUrl methods. However, they are made public so they can be used if needed.
        /// </remarks>
        /// <param name="existingValues"> Base parameter collection that you start with. </param>
        /// <param name="newValues"> Parameters that you add to the existing collection. </param>
        /// <param name="valuesToRemove"> Parameters that you want removed from the collection. </param>
        /// <returns> A query string. </returns>
        public static string CreateQueryString(Dictionary<string, string> existingValues, Dictionary<string, string> newValues, List<string> valuesToRemove)
        {
            StringBuilder queryString = new StringBuilder();
            Dictionary<string, string> newList = new Dictionary<string, string>(existingValues);

            if(newValues != null)
            {
                foreach(KeyValuePair<string, string> item in newValues)
                {
                    if(newList.ContainsKey(item.Key))
                        newList[item.Key] = item.Value;
                    else
                        newList.Add(item.Key, item.Value);
                }
            }

            if(valuesToRemove != null)
            {
                foreach(string item in valuesToRemove)
                {
                    if(newList.ContainsKey(item))
                        newList.Remove(item);
                }
            }

            int i = 0;
            string sep = String.Empty;
            foreach(KeyValuePair<string, string> item in newList)
            {
                queryString.AppendFormat("{0}{1}={2}", sep, item.Key, item.Value);

                if(i++ == 0)
                    sep = "&";
            }

            return queryString.ToString();
        }

        /// <summary>
        ///   Combines a path and a query string.
        /// </summary>
        /// <param name="targetPath"> The path. </param>
        /// <param name="queryString"> The query string. </param>
        /// <returns> A path plus its query string, if supplied. </returns>
        private static string CombinePathAndQueryString(string targetPath, string queryString)
        {
            string returnUrl = targetPath;

            if(!String.IsNullOrEmpty(queryString))
                returnUrl += "?" + queryString;

            return returnUrl;
        }

        /// <summary>
        ///   Extracts the query string values from a Uri and puts them in the supplied Dictionary.
        /// </summary>
        /// <remarks>
        ///   If a query string key already exists in the dictionary, its value will be replaced by the one in the Uri.
        /// </remarks>
        /// <param name="uri"> The Uri from which to take query string values. </param>
        /// <param name="queryStringValues"> The dictionary to which the query string values are to be added. </param>
        private static void ExtractQueryStringValuesFromUri(Uri uri, ref Dictionary<string, string> queryStringValues)
        {
            if(!String.IsNullOrEmpty(uri.Query))
            {
                string[] valuesFromCurrent = uri.Query.Replace("?", String.Empty).Split(new[] {'&'}, StringSplitOptions.RemoveEmptyEntries);
                foreach(string pair in valuesFromCurrent)
                {
                    string[] pairArray = pair.Split(new[] {'='}, StringSplitOptions.RemoveEmptyEntries);

                    if(pairArray.Length == 1)
                        queryStringValues.Add(pairArray[0], String.Empty);
                    else
                    {
                        if(queryStringValues.ContainsKey(pairArray[0]))
                            queryStringValues[pairArray[0]] = pairArray[1];
                        else
                            queryStringValues.Add(pairArray[0], pairArray[1]);
                    }
                }
            }
        }

        /// <summary>
        ///   Extracts the query string values from a string and puts them in the supplied dictionary.
        /// </summary>
        /// <remarks>
        ///   If a query string key already exists in the dictionary, its value will be replaced by the one in the supplied url string.
        /// </remarks>
        /// <param name="targetUrl"> The string from which to take query string values. </param>
        /// <param name="queryStringValues"> The dictionary to which the query string values are to be added. </param>
        public static void ExtractQueryStringValuesFromUrlString(string targetUrl, ref Dictionary<string, string> queryStringValues)
        {
            if(targetUrl.IndexOf("?", StringComparison.Ordinal) > -1)
            {
                int indexOfQuestionMark = targetUrl.IndexOf("?", StringComparison.Ordinal);
                if(indexOfQuestionMark < targetUrl.Length - 1)
                {
                    string[] valuesFromTarget = targetUrl.Substring(indexOfQuestionMark + 1, targetUrl.Length - indexOfQuestionMark - 1).Split(new[] {'&'},
                                                                                                                                               StringSplitOptions
                                                                                                                                                   .
                                                                                                                                                   RemoveEmptyEntries);

                    foreach(string pair in valuesFromTarget)
                    {
                        //split pair on equal sign
                        string[] pairArray = pair.Split(new[] {'='}, StringSplitOptions.RemoveEmptyEntries);

                        //if there is a key and a value, update the key or add both the key and value
                        if(pairArray.Length == 2)
                        {
                            //if Key exists in dictionary, update the value...
                            if(queryStringValues.ContainsKey(pairArray[0]))
                                queryStringValues[pairArray[0]] = pairArray[1];
                            else
                                //otherwise, add the name/value pair to the dictionary
                                queryStringValues.Add(pairArray[0], pairArray[1]);
                        }
                            //if only the key exists, add it to the dictionary if it doesnt already exist
                        else if(pairArray.Length == 1)
                        {
                            //if Key exists in dictionary, update the value...
                            if(!queryStringValues.ContainsKey(pairArray[0]))
                                queryStringValues.Add(pairArray[0], string.Empty);
                        }
                    }
                }
            }
        }

        /// <summary>
        ///   Returns a string value for a QueryString parmeter. Returns empty if the value isn't there.
        /// </summary>
        /// <param name="sParam"> </param>
        /// <param name="defaultValue"> </param>
        /// <returns> </returns>
        public static string GetParameter(string sParam, string defaultValue)
        {
            return HttpContext.Current.Request.QueryString[sParam] != null ? HttpContext.Current.Request[sParam] : defaultValue;
        }

        public static string Param(string paramName)
        {
            string result = String.Empty;

            if(HttpContext.Current.Request.Form.Count != 0)
                result = Convert.ToString(HttpContext.Current.Request.Form[paramName], CultureInfo.InvariantCulture);
            else if(HttpContext.Current.Request.QueryString.Count != 0)
                result = Convert.ToString(HttpContext.Current.Request.QueryString[paramName], CultureInfo.InvariantCulture);

            return (result == null) ? String.Empty : result.Trim();
        }

        /// <summary>
        ///   Returns an integer QueryString parameter. 0 if not present
        /// </summary>
        /// <param name="sParam"> </param>
        /// <param name="defaultValue"> </param>
        /// <returns> </returns>
        public static int GetIntParameter(string sParam, int defaultValue)
        {
            int iOut = defaultValue;
            if(HttpContext.Current.Request.QueryString[sParam] != null)
            {
                string sOut = HttpContext.Current.Request[sParam];
                if(!String.IsNullOrEmpty(sOut))
                    int.TryParse(sOut, out iOut);
            }
            return iOut;
        }

        public static T Value<T>(NameValueCollection nvc, string key, T defaultValue)
        {
            if(!String.IsNullOrEmpty(nvc[key]))
            {
                string possibleValue = nvc[key];
                TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(T));
                if(typeConverter.IsValid(possibleValue))
                    return (T)typeConverter.ConvertFromString(possibleValue);
            }
            return defaultValue;
        }

        public static string GetValueFromQueryString(string url, string key)
        {
            StringDictionary qsValues = ParseQuerystring(url);
            return qsValues.ContainsKey(key) ? qsValues[key] : string.Empty;
        }

        // //

        public static Uri ReplaceHost(Uri oldUrl, Uri newUrl)
        {
            if(oldUrl == null)
                throw new ArgumentNullException("oldUrl");
            if(newUrl == null)
                throw new ArgumentNullException("newUrl");

            return ReplaceHost(oldUrl.PathAndQuery, newUrl);
        }

        public static Uri ReplaceHost(string oldVirPath, Uri newUrl)
        {
            if(String.IsNullOrEmpty(oldVirPath))
                throw new ArgumentNullException("oldVirPath");
            if(newUrl == null)
                throw new ArgumentNullException("newUrl");

            return new UriBuilder {Scheme = newUrl.Scheme, Host = newUrl.Host, Path = oldVirPath}.Uri;
        }

        // //

        private static StringDictionary ParseQuerystring(string url)
        {
            StringDictionary queryDictionary = new StringDictionary();

            if(url != null && url.IndexOf("?") > 0)
            {
                string query = url.Substring(url.IndexOf("?") + 1);

                if(!string.IsNullOrEmpty(query))
                {
                    //split the query, omitting the leading "?"
                    string[] keyVals = query.Split('&');

                    foreach(string keyVal in keyVals)
                    {
                        if(string.IsNullOrEmpty(keyVal) || keyVal.IndexOf("=") < 0)
                            continue;
                        string key = keyVal.Substring(0, keyVal.IndexOf('='));
                        string val = keyVal.Substring(keyVal.IndexOf('=') + 1);

                        queryDictionary[key] = val;
                    }
                }
            }

            return queryDictionary;
        }

        private static string FlattenQuerystring(StringDictionary keyValuePairs)
        {
            if(keyValuePairs != null && keyValuePairs.Keys.Count > 0)
            {
                StringBuilder tempQuery = new StringBuilder("?");

                int i = 0;
                foreach(string key in keyValuePairs.Keys)
                {
                    tempQuery.AppendFormat("{0}={1}", key, keyValuePairs[key]);

                    if(i < (keyValuePairs.Keys.Count - 1))
                        tempQuery.Append("&");

                    i++;
                }

                return tempQuery.ToString();
            }
            return string.Empty;
        }

        public static string CleanUrlQuerystring(string url, params string[] preservedKeys)
        {
            if(url != null && url.IndexOf("?") > 0)
            {
                string path = url.Substring(0, url.IndexOf("?"));
                StringDictionary queryDictionary = ParseQuerystring(url);
                Array.Sort(preservedKeys);

                string[] keys = new string[queryDictionary.Keys.Count];
                queryDictionary.Keys.CopyTo(keys, 0);

                foreach(string key in keys)
                {
                    if(Array.BinarySearch(preservedKeys, key) < 0)
                        queryDictionary.Remove(key);
                }

                return path + FlattenQuerystring(queryDictionary);
            }
            return url;
        }

        public static string EnforceQuerystring(string url, params string[] enforcedKeyValuePairs)
        {
            string path = (url != null && url.IndexOf("?") > 0) ? url.Substring(0, url.IndexOf("?")) : url;
            StringDictionary queryDictionary = ParseQuerystring(url);

            //enforce key values
            foreach(string parmPair in enforcedKeyValuePairs)
            {
                string key = parmPair.Substring(0, parmPair.IndexOf('='));
                string val = parmPair.Substring(parmPair.IndexOf('=') + 1);

                queryDictionary[key] = val;
            }

            return path + FlattenQuerystring(queryDictionary);
        }

        public static string ToFullUrl(string relativeUrl)
        {
            string returnUrl = relativeUrl;

            if(!string.IsNullOrEmpty(relativeUrl))
            {
                Uri requestUrl = HttpContext.Current.Request.Url;

                if(relativeUrl[0] == '~')
                {
                    if(HttpContext.Current.Request.ApplicationPath == "/")
                        relativeUrl = relativeUrl.Substring(1, relativeUrl.Length - 1);
                    else
                        relativeUrl = HttpContext.Current.Request.ApplicationPath + relativeUrl.Substring(1, relativeUrl.Length - 1);

                    returnUrl = String.Format("{0}://{1}{2}", requestUrl.Scheme, requestUrl.Host, relativeUrl);
                }
                else if(relativeUrl[0] == '/') //parse into a full url, using the current request context
                    returnUrl = String.Format("{0}://{1}{2}", requestUrl.Scheme, requestUrl.Host, relativeUrl);
                else if(relativeUrl.Length > 2 && relativeUrl.Substring(0, 3) == "../")
                {
                    relativeUrl = relativeUrl.Replace("../", "/");
                    returnUrl = String.Format("{0}://{1}{2}", requestUrl.Scheme, requestUrl.Host, relativeUrl);
                }
            }

            return returnUrl;
        }

        public static string EnforceUriScheme(string absoluteUrl, string uriScheme)
        {
            if(absoluteUrl != null && absoluteUrl.IndexOf("://") < 0)
                absoluteUrl = ToFullUrl(absoluteUrl);

            if(absoluteUrl != null)
            {
                UriBuilder builder = new UriBuilder(absoluteUrl);
                builder.Scheme = uriScheme;
                return String.Format("{0}://{1}{2}{3}", builder.Scheme, builder.Host, builder.Path, builder.Query);
            }

            return null;
        }

        public static string EnforceNonSecureUri(string absoluteUrl)
        {
            return EnforceUriScheme(absoluteUrl, "http");
        }

        public static string EnforceSecureUri(string absoluteUrl)
        {
            return EnforceUriScheme(absoluteUrl, "https");
        }

        #region implementation

        private const string LINK = "<a href=\"{0}{1}\">{2}</a>";

        private static readonly Regex regex = new Regex("((http://|www\\.)([A-Z0-9.-:]{1,})\\.[0-9A-Z?;~&#=\\-_\\./]{2,})",
                                                        RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private static string ShortenUrl(string url, int max)
        {
            if(url.Length <= max)
                return url;

            // Remove the protocal
            int startIndex = url.IndexOf("://");
            if(startIndex > -1)
                url = url.Substring(startIndex + 3);

            if(url.Length <= max)
                return url;

            // Remove the folder structure
            int firstIndex = url.IndexOf("/") + 1;
            int lastIndex = url.LastIndexOf("/");
            if(firstIndex < lastIndex)
                url = url.Replace(url.Substring(firstIndex, lastIndex - firstIndex), "...");

            if(url.Length <= max)
                return url;

            // Remove URL parameters
            int queryIndex = url.IndexOf("?");
            if(queryIndex > -1)
                url = url.Substring(0, queryIndex);

            if(url.Length <= max)
                return url;

            // Remove URL fragment
            int fragmentIndex = url.IndexOf("#", StringComparison.Ordinal);
            if(fragmentIndex > -1)
                url = url.Substring(0, fragmentIndex);

            if(url.Length <= max)
                return url;

            // Shorten page
            firstIndex = url.LastIndexOf("/", StringComparison.Ordinal) + 1;
            lastIndex = url.LastIndexOf(".", StringComparison.Ordinal);
            if(lastIndex - firstIndex > 10)
            {
                string page = url.Substring(firstIndex, lastIndex - firstIndex);
                int length = url.Length - max + 3;
                url = url.Replace(page, "..." + page.Substring(length));
            }

            return url;
        }

        /// <summary>
        ///   Extracts the base path from the url, i.e., it will give you everything without the query string.
        /// </summary>
        /// <param name="targetUrl"> The url from which to extract. </param>
        /// <returns> Returns the path from the query string. </returns>
        private static string GetBasePathFromUrl(string targetUrl)
        {
            string targetPath = targetUrl.IndexOf("?", StringComparison.Ordinal) > -1
                                    ? targetUrl.Substring(0, targetUrl.IndexOf("?", StringComparison.Ordinal))
                                    : targetUrl;

            return targetPath;
        }

        #endregion
    }
}